package com.example.demo.lucene;

import org.apache.commons.io.FileUtils;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;

import org.apache.lucene.document.*;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.index.*;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.*;
import org.apache.lucene.search.highlight.*;
import org.apache.lucene.store.FSDirectory;

import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Paths;
import java.util.*;


public class LuceneManage {
    private  static LuceneConfig INDEX_DIR=null;//文件目录地址
    private  static LuceneManage luceneManage=null;
    private static SmartChineseAnalyzer analyzer=null;

    private static String DEFAUT_TABLE ="";//默认表
    //新增数字类型查询key值，（在5.0版本以上会出现数字排序报错例：
    // unexpected docvalues type NONE for field 'date' (expected=NUMERIC). Use UninvertingReader or index with docvalues.
    // 则需要使用NumericDocValuesField）
    //但操作查询时这个插入数据的数值类型不显示，我解决办法是：再添加一个搜索索引字段（使用NumericDocValuesField添加），
    // 保证原数据使用基本类型（如：LongField）添加；
    private static String  QUERY="_query";
    //lucene 对中文标题 进行索引 但不分词，检索的时候必须在中文标题加星号才能检索出来。（目前找到的办法，如果有新办法可以留言交流！）
    private static String sy="*";

    public final static LuceneManage getInstance(){
        if (luceneManage==null)
            return new LuceneManage();
        else
            return luceneManage;
    }

    private LuceneManage(){
       INDEX_DIR=LuceneConfig.getInstance();
       DEFAUT_TABLE=INDEX_DIR.getTable();
       analyzer=new SmartChineseAnalyzer();
    }


    /**
     *  创建表（文件）
     * @param tableName 表名称
     * @return
     */
    public boolean create_table(String tableName){
        boolean flag=true;
        File path = new File(INDEX_DIR.getPath());
        if (!path.isDirectory()){//检查文件地址是一个文件夹，且不是空文件夹就返回true，否则返回false，
            path.mkdirs();
        }
        if (!path.canRead()){//检测文件是否可执行可读可写权限
            path.setExecutable(true);
            path.setReadable(true);
            path.setWritable(true);
        }
        File doc = new File(INDEX_DIR.getPath(), tableName);
        if (doc.isDirectory()){//再次检查看文件目录是否创建
            flag=false;
        }
        else {
            doc.mkdirs();
        }
        if (!doc.canRead()){
            path.setExecutable(true);
            path.setReadable(true);
            path.setWritable(true);
        }
        return flag;

    }

    /**
     * 判断参考表是否存在
     * @param tabelName
     * @return
     */
    public boolean exist_table(String tabelName){
        File file = new File(INDEX_DIR.getPath(), tabelName);
        return file.exists();
    }

    /**
     * 删除表
     * @param tableName
     * @return
     */
    public boolean delete_table(String tableName){
        boolean flag=true;
        File doc = new File(INDEX_DIR.getPath(), tableName);
        if (doc.isDirectory()){
            try {
                FileUtils.deleteDirectory(doc);
            } catch (IOException e) {
                flag=false;
            }
        }else {
            flag=false;
        }
        return flag;
    }

    public void clear_table_date(String tableName){
        delete_table(tableName);
        create_table(tableName);
    }






    /**
     * 插入数据
     * @param list
     * @param tableName
     * @return
     */


    public boolean add(List<List<Field>> list, String tableName){
        FSDirectory open=null;
        IndexWriter indexWriter=null;
        File indexPath= new File(INDEX_DIR.getPath(), tableName);
        try {
            open = FSDirectory.open(Paths.get(indexPath.toURI()));
            indexWriter = _indexWriter(open, tableName);
            for (List<Field> fields : list) {
                Document doc = new Document();
                    for (Field field : fields) {
                        switch (field.getType()){
                            case Key:
                                QueryParser parser = new QueryParser(field.getName(), analyzer);
                                Query parse = parser.parse(field.getValue());
                                indexWriter.deleteDocuments(parse);
//                                indexWriter.deleteDocuments(new Term(field.getName(),field.getValue())); //删除主键
                                doc.add(new StringField(field.getName(),field.getValue(), Store.YES));break;
                            case String:
                                doc.add(new StringField(field.getName(),field.getValue(), Store.YES));break;
                            case Text:
                                doc.add(new TextField(field.getName(),field.getValue(), Store.YES));break;
                            case Stored:
                                doc.add(new StoredField(field.getName(),field.getValue()));break;
                            case Double:
                                doc.add(new DoubleField(field.getName(),Double.parseDouble(field.getValue()), Store.YES));
                                break;
                            case Long:
                                doc.add(new NumericDocValuesField(field.getName()+QUERY,Long.parseLong(field.getValue())));
                                doc.add(new LongField(field.getName(),Long.parseLong(field.getValue()), Store.YES));
                                break;
                            case Integer:
                                doc.add(new NumericDocValuesField(field.getName()+QUERY,Long.parseLong(field.getValue())));
                                doc.add(new IntField(field.getName(),Integer.parseInt(field.getValue()), Store.YES));
                                break;
                        }
                    }
                indexWriter.addDocument(doc);
            }
            indexWriter.commit();
        } catch (IOException e) {
            return false;
        } catch (ParseException e) {
            e.printStackTrace();
            return false;
        } finally {
            try {
                if (indexWriter!=null)indexWriter.close();
                if (open!=null)open.close();
            } catch (IOException e) {
                return false;
            }
        }
        return true;
    }

    private IndexWriter _indexWriter(FSDirectory fsDirectory, String tableName){
        IndexWriter indexWriter=null;
        try {
            IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
            indexWriter =new IndexWriter(fsDirectory,indexWriterConfig);
        } catch (IOException e) {
            e.printStackTrace();
            _indexWriter(fsDirectory,tableName);
        }
        return indexWriter;
    }

    /**
     * 根据主键key删除数据
     * @param field
     * @param tableName
     * @return
     */
    public int delete(Field field, String tableName) {

        if (!exist_table(tableName)) return  ResultSet.ERROR_NOT_FOUND;
        File indexPath= new File(INDEX_DIR.getPath(), tableName);
        FSDirectory directory=null;
        IndexWriter iwriter=null;
        try {
            directory = FSDirectory.open(Paths.get(indexPath.toURI()));
            iwriter = _indexWriter(directory, tableName);
            QueryParser parser = new QueryParser(field.getName(), analyzer);
            Query parse = parser.parse(field.getValue());
            iwriter.deleteDocuments(parse);
            iwriter.commit();

        } catch (IOException e) {
            return ResultSet.ERROR_SYSTEM; //系统Lucene错误 500
        } catch (ParseException e) {
            e.printStackTrace();
        } finally
        {
            try {
                if(iwriter!=null)iwriter.close(); //关闭文件锁
                if(directory!=null)directory.close();
            } catch (IOException e) {
                return ResultSet.ERROR_SYSTEM;//系统Lucene错误 500
            }

        }
        return ResultSet.SUCCESS_OK; //200成功状态码
    }


    /**
     *
     * @param fields
     * @param sortFields
     * @param tableNames
     * @return
     */

    public Page select(int start, int size, Field[] fields, SortField[] sortFields, String...tableNames){
        //查询表是否存在！
        List<Map<String,Object>> list = new ArrayList<>();
        int total=0;
        try {
            IndexSearcher indexSearcher = _indexSearcher(tableNames);
            BooleanQuery.Builder builder = new BooleanQuery.Builder();
            String[] sfields = new String[fields.length];
            for (int i = 0; i < fields.length; i++) {
                //考虑查询参数类型不同而进行复合多条件查询
                if (fields[i]!=null) {
                    sfields[i] = fields[i].getName();
                    Field field = fields[i];
                    String value = fields[i].getValue();
                    String name = fields[i].getName();
                    switch (field.getType()) {
                        case String:

                            QueryParser queryParser_String = new QueryParser(name, analyzer);
                            Query str_q = queryParser_String.parse(value+sy);
                            builder.add(str_q, BooleanClause.Occur.MUST);
                            break;
                        case Text:
                            QueryParser queryParser_Text = new QueryParser(name, analyzer);
                            Query text_q = queryParser_Text.parse(value);
                            builder.add(text_q, BooleanClause.Occur.SHOULD);
                            break;
                        case Double:
                            if (value.contains("#")) {
                                NumericRangeQuery<Double> double_q = NumericRangeQuery.newDoubleRange(name, Double.parseDouble(value.split("#")[0]), Double.parseDouble(value.split("#")[1]), false, true);
                                builder.add(double_q, BooleanClause.Occur.MUST);
                            }else {
                                NumericRangeQuery<Double> double_q = NumericRangeQuery.newDoubleRange(name, Double.parseDouble(value)-1, Double.parseDouble(value), false, true);
                                builder.add(double_q, BooleanClause.Occur.MUST);
                            }
                            break;
                        case Long:
                            if (value.contains("#")) {
                                NumericRangeQuery<Long> long_q = NumericRangeQuery.newLongRange(name, Long.parseLong(value.split("#")[0]), Long.parseLong(value.split("#")[1]), false, true);
                                builder.add(long_q, BooleanClause.Occur.MUST);
                            }
                            else {
                                NumericRangeQuery<Long> long_q = NumericRangeQuery.newLongRange(name, Long.parseLong(value)-1, Long.parseLong(value), false, true);
                                builder.add(long_q, BooleanClause.Occur.MUST);
                            }
                            break;
                        case Integer:
                            if (value.contains("#")) {
                                NumericRangeQuery<Integer> integer_q = NumericRangeQuery.newIntRange(name, Integer.parseInt(value.split("#")[0]), Integer.parseInt(value.split("#")[1]), false, true);
                                builder.add(integer_q, BooleanClause.Occur.MUST);
                            }
                            else {
                                NumericRangeQuery<Integer> integer_q = NumericRangeQuery.newIntRange(name, Integer.parseInt(value)-1, Integer.parseInt(value), false, true);
                                builder.add(integer_q, BooleanClause.Occur.MUST);
                            }

                    }
                }
            }
            BooleanQuery build = builder.build();
            TopDocs topDocs =null;
            if (sortFields==null)
                topDocs = indexSearcher.search(build,INDEX_DIR.getSize());
            else
                topDocs = indexSearcher.search(build,INDEX_DIR.getSize(),new Sort(sortFields));
            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
            total=0;
            for (int i = 0; i < scoreDocs.length&&i<(start+size); i++) {
                Map<String,Object> map = new HashMap<>();

//                if (scoreDocs[i].doc>=100) {//调试百分比
                    Document doc = indexSearcher.doc(scoreDocs[i].doc);
                    List<IndexableField> field = doc.getFields();
                    field.forEach(e -> {
                        map.put(e.name(), e.stringValue());
                    });
                    list.add(map);
                    total++;
//                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return new Page(start,size,total,list);
    }

    public int update(List<Map<String,List<Field>>> list, String tableName){
        if (!exist_table(tableName)) return  ResultSet.ERROR_NOT_FOUND;
        File indexPath= new File(INDEX_DIR.getPath(), tableName);
        FSDirectory directory=null;
        IndexWriter indexWriter=null;
        try {
            directory = FSDirectory.open(Paths.get(indexPath.toURI()));
            indexWriter = _indexWriter(directory, tableName);
            for (Map<String, List<Field>> stringListMap : list) {
                Set<String> keySet = stringListMap.keySet();
                Document doc = new Document();
                String value=null;
                String key = null;
                for (String s : keySet) {
                    List<Field> fields =(List<Field>) stringListMap.get(s);
                    for (Field field : fields) {
                        switch (field.getType()){
                            case Key:
                                value=field.getValue();
                                key=field.getName();
                                doc.add(new StringField(field.getName(),field.getValue(), Store.YES));break;
                            case String:
                                doc.add(new StringField(field.getName(),field.getValue(), Store.YES));break;
                            case Text:
                                doc.add(new TextField(field.getName(),field.getValue(), Store.YES));break;
                            case Stored:
                                doc.add(new StoredField(field.getName(),field.getValue()));break;
                            case Double:
                                doc.add(new DoubleField(field.getName(),Double.parseDouble(field.getValue()), Store.YES));break;
                            case Long:
                                doc.add(new LongField(field.getName(),Long.parseLong(field.getValue()), Store.YES));break;
                            case Integer:
                                doc.add(new IntField(field.getName(),Integer.parseInt(field.getValue()), Store.YES));break;
                        }
                    }
                }
                if (key!=null&&value!=null)

                    indexWriter.updateDocument(new Term(key,value),doc);
            }
            indexWriter.commit();

        } catch (IOException e) {
            return ResultSet.ERROR_SYSTEM; //系统Lucene错误 500
        }  finally
        {
            try {
                if(indexWriter!=null)indexWriter.close(); //关闭文件锁
                if(directory!=null)directory.close();
            } catch (IOException e) {
                return ResultSet.ERROR_SYSTEM;//系统Lucene错误 500
            }

        }
        return ResultSet.SUCCESS_OK; //200成功状态码

    }


    private void  _highlighted(List<Map<String,Object>> list, Query parse, TopDocs search, IndexSearcher searcher, String[] sfields) throws IOException, InvalidTokenOffsetsException {
        SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<b><font color=red>", "</font></b>");
        QueryScorer queryScorer = new QueryScorer(parse);//计算查询结果最高得分
        Fragmenter fragmenter = new SimpleSpanFragmenter(queryScorer);//根据得分算出一个片段
        Highlighter highlighter = new Highlighter(simpleHTMLFormatter, queryScorer);
        highlighter.setTextFragmenter(fragmenter);//设置显示高亮的片段
        //遍历查询结果
        for (ScoreDoc scoreDoc : search.scoreDocs) {
           Map<String,Object> map = new HashMap<>();
            Document doc = searcher.doc(scoreDoc.doc);
            List<IndexableField> fields = doc.getFields();
            String tcontent = doc.get("title");
            TokenStream tokenStream = analyzer.tokenStream("title", new StringReader(tcontent));
            String summary = highlighter.getBestFragment(tokenStream,tcontent);
            System.out.println(summary);
            map.put("title",summary);
            for (IndexableField field : fields) {
                //这里显示高亮只是单独查询title显示
                    if (!field.name().equals("title")){
                        map.put(field.name(),field.stringValue());
                    }
            }
            list.add(map);
        }
    }


    private IndexSearcher _indexSearcher(String...tableNames) throws IOException {
        if (tableNames==null){
            tableNames=new String[1];
            tableNames[0]=DEFAUT_TABLE;
        }
        IndexReader[] indexReader = new IndexReader[tableNames.length];
        int idx = 0;
        for (String tableName : tableNames) {
            File file = new File(INDEX_DIR.getPath(), tableName);
            FSDirectory directory = FSDirectory.open(Paths.get(file.toURI()));
            indexReader[idx++]  = DirectoryReader.open(directory);
        }
        IndexSearcher indexSearcher = new IndexSearcher(new MultiReader(indexReader, true));
        return indexSearcher;
    }



}
